/*
 * @features: 功能
 * @description: 说明
 * @Date: 2022-01-02 17:52:43
 * @Author: judu233(769471424@qq.com)
 * @LastEditTime: 2022-08-28 19:57:36
 * @LastEditors: judu233
 */

import { ExtendsLoad } from "../CCExtends";

const ACTION_POOL: Map<string, cc.Tween> = new Map();

/**扩展原型组件 */
@ExtendsLoad(cc.Tween)
export class TweenBaseExtends {
    /**
    * 使节点变换成指定透明度
    * @param node 节点
    * @param opacity 透明度
    * @param duration 时间
    * @returns 
    */
    static fadeTo(node: cc.Node, opacity: number, duration = 0.35) {
        return new Promise<void>(resolve => {
            if (node) {
                let t = cc.tween(node)
                    .to(duration, { opacity: opacity })
                    .call(() => {
                        ACTION_POOL.delete(node.name);
                        resolve();
                    })
                    .start();
                ACTION_POOL.set(node.name, t);
            } else {
                resolve();
                console.log(`要渐变的节点为空`);
            }
        });
    }

    /**
     * 使节点渐渐隐藏到0
     * @param node 要渐隐的节点
     * @param duration 时长
     */
    static fadeOut(node: cc.Node, duration = 0.35) {
        return cc.Tween.fadeTo(node, 0, duration);
    }

    /**
    * 使节点渐渐显示到255
    * @param node 要渐显的节点
    * @param duration 时长
    */
    static fadeIn(node: cc.Node, duration = 0.35) {
        if (node) {
            node.opacity = 0;
            node.active = true;
            return cc.Tween.fadeTo(node, 255, duration);
        } else {
            console.log(`要渐变的节点为空`);
        }
    }

    /**
    * 缩放节点到指定大小
    * @param node 要缩放的节点
    * @param to 缩放大小(整体)
    * @param duraction 动画时间
    * @returns promise
    */
    static scaleTo(node: cc.Node, to: cc.Vec3 | number, duraction = 0.35) {
        return new Promise<void>(resolve => {
            if (node) {
                let toV3 = to instanceof cc.Vec3 ? to : cc.v3(to, to, to);
                let t = cc.tween(node)
                    .to(duraction, { scaleX: toV3.x, scaleY: toV3.y, scaleZ: toV3.z }, cc.easeSineIn())
                    .call(() => ACTION_POOL.delete(node.name))
                    .call(resolve)
                    .start()
                ACTION_POOL.set(node.name, t);
            } else {
                console.log(`要移动的节点为空`);
                resolve();
            }
        });
    }

    /**
     * 缩放节点相对大小
     * @param node 缩放节点
     * @param to 缩放值(整体)
     * @param duraction 时间
     * @returns promise
     */
    static scaleBy(node: cc.Node, to: cc.Vec3 | number, duraction = 0.35) {
        return new Promise<void>(resolve => {
            if (node) {
                let toV3 = to instanceof cc.Vec3 ? to : cc.v3(to, to, to);
                let t = cc.tween(node)
                    .by(duraction, { scaleX: toV3.x, scaleY: toV3.y, scaleZ: toV3.z }, cc.easeSineIn())
                    .call(() => ACTION_POOL.delete(node.name))
                    .call(resolve)
                    .start()
                ACTION_POOL.set(node.name, t);
            } else {
                console.log(`要缩放的节点为空`);
                resolve();
            }
        });
    }

    /**
     * 移动节点到指定位置
     * @param node 节点
     * @param to 目标位置
     * @param duraction 时间
     * @returns promise
     */
    static moveTo(node: cc.Node, to: cc.Vec3, duraction = 0.35) {
        return new Promise<void>(resolve => {
            if (node) {
                let t = cc.tween(node)
                    .to(duraction, { position: to }, cc.easeSineIn())
                    .call(() => ACTION_POOL.delete(node.name))
                    .call(resolve)
                    .start();
                ACTION_POOL.set(node.name, t);
            } else {
                console.log(`要移动的节点为空`);
                resolve();
            }
        });
    }


    /**
    * 移动节点相对位置
    * @param node 节点
    * @param to 目标位置
    * @param duraction 时间
    * @returns promise
    */
    static moveBy(node: cc.Node, by: cc.Vec3, duraction = 0.35, easing = cc.easeSineIn) {
        return new Promise<void>(resolve => {
            if (node) {
                let t = cc.tween(node)
                    .by(duraction, { position: by }, easing())
                    .call(() => ACTION_POOL.delete(node.name))
                    .call(resolve)
                    .start()
                ACTION_POOL.set(node.name, t);
            } else {
                console.log(`要移动的节点为空`);
                resolve();
            }
        });
    }

    /**
     * 2个节点交互显示闪烁的效果
     * @param node1 节点1 开始时隐藏
     * @param node2 节点2 开始时显示
     * @param count 闪烁次数
     * @param timeList 时间参数
     * @param defauletValue 默认时间值
     */
    static node2Link(node1: cc.Node, node2: cc.Node, count = 2, timeList = [0.1, 0.2, 0.3, 0.2, 0.1, 0.3], defauletValue = 0.1) {
        node2.active = node1.active = true;
        node1.opacity = 255;
        node2.opacity = 0;
        for (let i = 0; i < count; i++) {
            // await cc.Tween.fadeOut(node1, timeList[0] ?? defauletValue);
            // await cc.Tween.fadeIn(node2, timeList[1] ?? defauletValue);
            // await Tool.promise.delay(timeList[2] ?? defauletValue);
            // await cc.Tween.fadeOut(node2, timeList[3] ?? defauletValue);
            // await cc.Tween.fadeIn(node1, timeList[4] ?? defauletValue);
            // await Tool.promise.delay(timeList[5] ?? defauletValue);
        }
    }

    /**
      * 多个节点同时进行动画
      * @param parmeList 节点-参数列表
      * @param animCall 动画回调
      * @returns  Pomise
      * @example GM.anim.animPlayList([[node, cc.v3(0, -this.downValue)], [node, cc.v3(0, -this.downValue)]], GM.anim.moveBy);
      */
    static animPlayList(parmeList: [cc.Node, ...any][], animCall: Function) {
        let list = [];
        parmeList.forEach(parme => {
            list.push(animCall.call(this, ...parme));
        });
        return Promise.all(list);
    }
    /**
      * 多个节点同时进行动画
      * @param parmeList 节点-参数列表
      * @param animCall 动画回调
      * @returns  Pomise
      * @example GM.anim.animPromise([cc.tween(node).move(), cc.tween(node).scale()]);
      */
    static animPromise(animList: cc.Tween<unknown>[]) {
        let p: Promise<void>[] = [];
        for (let anim of animList) {
            p.push(new Promise(resolve => {
                anim.call(resolve).start();
            }));
        }
        return Promise.all(p);
    }

    static playSquAnimList(tweenList: cc.Tween[]) {
        let animParme = {
            _finish: () => { },
            finish: function (call: () => void) {
                this._finish = call;
            },
            _everyCall: (curT: cc.Tween, nextT: cc.Tween) => { },
            everyCall: function (call: (curT: cc.Tween, nextT: cc.Tween) => void) {
                this._everyCall = call;
            },
            stopAll: () => {
                tweenList.forEach(t => t.stop());
            },
            stop: (index: number) => { tweenList[index].stop() }
        };
        for (let [index, t] of Object.entries(tweenList)) {
            t.call(() => {
                let next = Number(index) + 1;
                let nextTw = tweenList[Number(next)];
                animParme._everyCall(t, nextTw);
                if (nextTw) {
                    nextTw.start();
                } else {
                    animParme._finish();
                }
            });
        }
        tweenList[0].start();
        return animParme;
    }

    /**
     * 闪烁指定次数
     * @param node 要link闪烁的节点
     * @param count 闪烁的次数 ,-1重复闪烁
     * @param linkTime 闪烁的时间
     * @returns 
     */
    static link(node: cc.Node, count = -1, linkTime = 0.3, maxOpacity = 255, minOpacity = 0) {
        if (!node) return;
        node.active = true;
        node.opacity = 0;
        let t: cc.Tween<cc.Node>;
        let p = new Promise<void>(resolve => {
            let tt = cc.tween(node)
                .to(linkTime, { opacity: maxOpacity }, cc.easeSineIn())
                .delay(0.1)
                .to(linkTime, { opacity: minOpacity }, cc.easeSineIn())
                .delay(0.1)
                .union();
            if (count <= 0) {
                t = tt.repeatForever();
            } else {
                t = tt.repeat(count)
                    .call(() => {
                        resolve();
                    });
            }
            t = cc.tween(node)
                .then(tt)
                .start();
        })
        return {
            t,
            then: p.then.bind(p),
        }
    }

    /**
     * 按钮动画
     * @param node 按钮节点
     * @returns promise`
     */
    static buttonAnim(node: cc.Node) {
        return new Promise<cc.Node | void>(resolve => {
            if (node) {
                let v3_t = cc.v3();
                node.getScale(v3_t);
                cc.tween(node)
                    .to(.3, { scaleX: v3_t.x += .25, scaleY: v3_t.y += .1 })
                    .to(.3, { scaleX: v3_t.x -= .25, scaleY: v3_t.y -= .1 }, { easing: "bounceOut" })
                    .call(() => {
                        resolve(node);
                    })
                    .start();
            } else
                resolve();
        });
    }

    static breatheAnim(node: cc.Node, animType: "bianPao" | "zhaDan") {
        let v3_scale_default_t = cc.v3();
        // let v3_position_t = cc.v3();
        node.getScale(v3_scale_default_t);
        // node.getPosition(v3_position_t);
        switch (animType) {
            case "bianPao":
                return cc.tween(node)
                    .to(.3, { scaleX: v3_scale_default_t.x * .9, scaleY: v3_scale_default_t.y * .95 },
                        { easing: "fade" })
                    .to(.3, { scaleX: v3_scale_default_t.x * 1.07, scaleY: v3_scale_default_t.y * 1 },
                        { easing: "circInOut" })
                    .to(.1, { scaleX: v3_scale_default_t.x, scaleY: v3_scale_default_t.y })
                    .delay(1)
                    .union()
                    .repeatForever()
                    .start();

            case "zhaDan":
                return cc.tween(node)
                    .to(.8, { scaleX: v3_scale_default_t.x * 1.2, scaleY: v3_scale_default_t.y * 1.2 },
                        { easing: "backOut" })
                    .to(.5, { scaleX: v3_scale_default_t.x, scaleY: v3_scale_default_t.y })
                    .union()
                    .repeatForever()
                    .start();
        }
    }


    /**
     * 窗口隐藏
     * @param node 节点
     * @returns promise
     */
    static winHide(node: cc.Node) {
        return new Promise<void>(resolve => {
            if (node) {
                cc.tween(node)
                    .to(.2, { scaleX: 0, scaleY: 0 })
                    .call(resolve)
                    .start()
            } else
                resolve();
        });
    }

    /**
     * 窗口显示
     * @param node 节点
     * @returns promise
     */
    static winShow(node: cc.Node) {
        return new Promise<void>(resolve => {
            if (node) {
                node.setScale(0, 0);
                cc.tween(node)
                    // 不能直接调用 当前帧爆发动画会导致卡帧
                    // .delay(0.001)
                    .to(.3, { scaleX: 1, scaleY: 1.05 }, { easing: "backOut" })
                    .to(.2, { scaleX: .98, scaleY: .96 })
                    .to(.25, { scaleX: 1, scaleY: 1.02 })
                    .to(.1, { scaleX: 1, scaleY: 1 })
                    .call(resolve)
                    .union()
                    .start()
            } else
                resolve();
        });
    }

    /**
    * 无限旋转动画
    * @param node
    */
    static rotate(node: cc.Node) {
        return new Promise<void>(resolve => {
            if (node) {
                cc.tween(node)
                    .by(5, { angle: 360 })
                    .repeatForever()
                    .start();
                resolve();
            } else
                resolve();
        });
    }


    /**
     * 水平翻转（卡片翻转）
     * @param node 节点
     * @param duration 总时长
     * @param onMiddle 中间状态回调
     * @param onComplete 完成回调
     */
    static flip(node: cc.Node, duration: number, onMiddle?: Function, onComplete?: Function): Promise<void> {
        return new Promise<void>(res => {
            const t = cc.tween, time = duration / 2,
                scaleX = node.scale, skewY = scaleX > 0 ? 20 : -20;
            t(node)
                .parallel(
                    t().to(time, { scaleX: 0 }, { easing: 'quadIn' }),
                    t().to(time, { skewY: -skewY }, { easing: 'quadOut' }),
                )
                .call(() => {
                    onMiddle && onMiddle();
                })
                .parallel(
                    t().to(time, { scaleX: -scaleX }, { easing: 'quadOut' }),
                    t().to(time, { skewY: 0 }, { easing: 'quadIn' }),
                )
                .call(() => {
                    onComplete && onComplete();
                    res();
                })
                .start();
        });
    }

    /**
     * 数字滚动
     * @param node 
     * @param start 
     * @param end 
     * @param duration 
     * @param dt_cb 
     * @param cb 
     */
    static roll_num(node: cc.Node & { roll_num: number }, start: number, end: number, duration: number,
        dt_cb?: (value: number) => void, cb?: () => void
    ) {
        node["roll_num"] = start;
        let tween = cc.tween(node)
            .to(duration, { roll_num: end }, {
                progress: (start: number, end: number, current: number, t: number) => {
                    current = start + (end - start) * t;
                    if (dt_cb)
                        dt_cb(current)
                    return current;
                }
            })
            .call(cb)
            .start()
        return tween as cc.Tween;
    }


    /**
     * 播放弹性移动效果
     * @param targetPos 目标位置
     * @param time 移动时间
     * @param frequency 频率（弹跳次数）
     * @param decay 衰退指数
     * @see BounceMoveTween.ts https://gitee.com/ifaswind/eazax-ccc/blob/master/components/tweens/BounceMoveTween.ts
     */
    static playBounceMoveTween(node: cc.Node, targetPos: cc.Vec2, time: number, frequency = 4, decay = 2,) {
        // 当前位置
        const curPos = node.getPosition();
        // 方向
        const direction = targetPos.sub(curPos).normalize();
        // 时长
        const bouncingTime = 0.75;  // 弹跳时长
        // 振幅
        const amplitude = cc.Vec2.distance(curPos, targetPos) / time;
        /**
         * 获取目标时刻弹性幅度
         * @param amplitude 幅度
         * @param time 时间
         */
        let getDifference = (amplitude: number, time: number) => {
            // 角速度（ω=2nπ）
            const angularVelocity = frequency * Math.PI * 2;
            return amplitude * (Math.sin(time * angularVelocity) / Math.exp(decay * time) / angularVelocity);
        }
        // 播放
        let tween = cc.tween(node)
            .to(time, { x: targetPos.x, y: targetPos.y }, { easing: 'quadIn' })
            .to(bouncingTime, {
                position: {
                    //@ts-expect-error
                    value: cc.v3(targetPos.x, targetPos.y),
                    progress: (start: cc.Vec3, end: cc.Vec3, current: cc.Vec3, t: number) => {
                        const pos = direction.mul(-getDifference(amplitude, t));
                        return cc.v3(pos.x, pos.y);
                    }
                }
            })
            .start();
        return { tween, stop: () => tween.stop() }
    }


    /**
     * 播放弹性缩放效果
     * @param node 节点
     * @param targetScale 目标缩放
     * @param repeatTimes 重复次数
     * @param frequency 频率（弹跳次数）
     * @param decay 衰退指数
     * @param totalTime 效果总时长
     * @param interval 播放间隔
     * @see BounceScaleTween.ts https://gitee.com/ifaswind/eazax-ccc/blob/master/components/tweens/BounceScaleTween.ts
     */
    static playBounceScaleTween(node: cc.Node, targetScale = 1, repeatTimes: number = 1, frequency = 4, decay = 2, totalTime = 1, interval = 1) {
        /** 原始缩放值 */
        let originalScale = node.scale;
        // 重复次数
        const times = (repeatTimes != undefined && repeatTimes > 0) ? repeatTimes : -1;
        // 时长
        const scalingTime = totalTime * 0.25;   // 缩放时长
        const bouncingTime = totalTime * 0.75;  // 弹跳时长
        // 振幅
        const amplitude = (targetScale - originalScale) / scalingTime;
        /**
         * 获取目标时刻弹性幅度
         * @param amplitude 幅度
         * @param time 时间
         */
        let getDifference = (amplitude: number, time: number) => {
            // 角速度（ω=2nπ）
            const angularVelocity = frequency * Math.PI * 2;
            return amplitude * (Math.sin(time * angularVelocity) / Math.exp(decay * time) / angularVelocity);
        }
        // 播放
        let tween = cc.tween(node)
            .repeat(times, cc.tween()
                .set({ scale: originalScale })
                .to(scalingTime, { scale: targetScale })
                .to(bouncingTime, {
                    scale: {
                        value: targetScale,
                        progress: (start: number, end: number, current: number, t: number) => {
                            return end + getDifference(amplitude, t);
                        }
                    }
                })
                .delay(interval)
            )
            .start();
        return {
            tween, stop: () => {
                tween.stop();
                node.setScale(originalScale);
            }
        }
    }

    /**
     * 播放果冻缓动效果
     * @param node 重复次数
     * @param repeatTimes 重复次数
     * @param frequency 频率（弹跳次数）
     * @param decay 衰退指数
     * @param pressScale 下压缩放
     * @param totalTime 效果总时长
     * @param interval 播放间隔
     * @see JellyTween.ts https://gitee.com/ifaswind/eazax-ccc/blob/master/components/tweens/JellyTween.ts
     */
    static playJellyTween(node: cc.Node, repeatTimes?: number, frequency = 4, decay = 2, pressScale = 0.2, totalTime = 1, interval = 1,) {
        let originalScale = node.scale;
        // 重复次数
        const times = (repeatTimes != undefined && repeatTimes > 0) ? repeatTimes : 10e8;
        // 时长
        const pressTime = totalTime * 0.2;         // 收缩时长
        const scaleBackTime = totalTime * 0.15;    // 缩放至原始大小时长
        const bouncingTime = totalTime * 0.65;     // 弹动时长
        // 振幅
        const amplitude = pressScale / scaleBackTime;
        /**
         * 获取目标时刻弹性幅度
         * @param amplitude 幅度
         * @param time 时间
         */
        let getDifference = (amplitude: number, time: number) => {
            // 角速度（ω=2nπ）
            const angularVelocity = frequency * Math.PI * 2;
            return amplitude * (Math.sin(time * angularVelocity) / Math.exp(decay * time) / angularVelocity);
        }
        // 播放
        let tween = cc.tween(node)
            .repeat(times, cc.tween()
                .to(pressTime, { scaleX: originalScale + pressScale, scaleY: originalScale - pressScale }, { easing: 'sineOut' })
                .to(scaleBackTime, { scaleX: originalScale, scaleY: originalScale })
                .to(bouncingTime, {
                    scaleX: {
                        value: originalScale,
                        progress: (start: number, end: number, current: number, t: number) => {
                            return end - getDifference(amplitude, t);
                        }
                    },
                    scaleY: {
                        value: originalScale,
                        progress: (start: number, end: number, current: number, t: number) => {
                            return end + getDifference(amplitude, t);
                        }
                    }
                })
                .delay(interval)
            )
            .start();

        return {
            tween, stop: () => {
                tween.stop();
                node.setScale(originalScale);
            }
        }
    }

}
/**扩展组件实例方法 */
@ExtendsLoad(cc.Tween.prototype)
export class TweenExtends {
    /**
     * 抛物线创建
     * @param durationTime 
     * @param startPoint 
     * @param endPoint 
     * @param height 
     * @param angle 
     * @returns 
     */
    static createBezierTo(durationTime: number, startPoint: cc.Vec2, endPoint: cc.Vec2, height: number, angle: number) {
        let tween = this as any as cc.Tween<cc.Node>;
        // 把角度转换为弧度
        let radian = angle * 3.14159 / 180;
        // 第一个控制点为抛物线左半弧的中点
        let q1x = startPoint.x + (endPoint.x - startPoint.x) / 4;
        let q1 = cc.v2(q1x, height + startPoint.y + Math.cos(radian) * q1x);
        // 第二个控制点为整个抛物线的中点
        let q2x = startPoint.x + (endPoint.x - startPoint.x) / 2;
        let q2 = cc.v2(q2x, height + startPoint.y + Math.cos(radian) * q2x);
        tween.then(cc.bezierTo(durationTime, [q1, q2, endPoint]).easing(cc.easeInOut(0.5)));
        return tween;
    }
}
